import { useCallback, useMemo, useState } from "react";
import isHotkey from "is-hotkey";
import Btn from "./components/toolBtn/Btn";
import { Editable, withReact, useSlate, Slate, ReactEditor } from "slate-react";
import {
  Editor,
  Transforms,
  createEditor,
  Text,
  Descendant,
  Element as SlateElement,
  Range,
} from "slate";
import { withHistory } from "slate-history";
import withLayout from "./plugin/layout";
import CheckListItem from "./components/checkList/CheckList";
import Image from "./components/image";
import "./main.css";
import style from "./main.css";
const HOTKEYS = {
  "mod+b": "bold",
  "mod+i": "italic",
  "mod+u": "underline",
  "mod+`": "code",
};

export type LinkElement = { type: "link"; url: string; children: Descendant[] };
interface EditorLeaf extends Text {
  [name: string]: any;
}
interface Elements extends SlateElement {
  [name: string]: any;
}
interface EditorElement extends SlateElement {
  [name: string]: any;
  children: Array<Elements | EditorLeaf>;
}
type EditorNode = EditorLeaf | EditorElement;

const LIST_TYPES = ["ol", "ul"];

const Edit = () => {
  const [value, setValue] = useState<EditorNode[]>(initialValue);
  const [search, setSearch] = useState<string | undefined>();
  const renderElement = useCallback((props) => <Element {...props} />, []);
  const renderLeaf = useCallback((props) => <Leaf {...props} />, []);
  const editor = useMemo(
    () => withLayout(withHistory(withReact(createEditor() as ReactEditor))),
    []
  );
  const decorate = useCallback(
    ([node, path]) => {
      const ranges = [];

      if (search && Text.isText(node)) {
        const { text } = node;
        const parts = text.split(search);
        let offset = 0;

        parts.forEach((part, i) => {
          if (i !== 0) {
            ranges.push({
              anchor: { path, offset: offset - search.length },
              focus: { path, offset },
              highlight: true,
            });
          }

          offset = offset + part.length + search.length;
        });
      }

      return ranges;
    },
    [search]
  );
  return (
    <>
      <Slate
        editor={editor}
        value={value}
        onChange={(value) => {
          console.log(value);
          setValue(value as EditorNode[]);
        }}
      >
        <div className={style.toolbar}>
          <MarkButton type="bold" />
          <MarkButton type="code" />
          <MarkButton type="delete" />
          <MarkButton type="itaily" />
          <MarkButton type="underline" />
          <BlockButton type="h1" />
          <BlockButton type="quote" />
          <BlockButton type="ul" />
          <BlockButton type="ol" />
          <BlockButton type="check" />
          <LinkButton />
          <RemoveLinkButton />
          <div style={{ height: 40 }}>
            <input
              type="search"
              placeholder="Search the text..."
              onChange={(e) => setSearch(e.target.value)}
              style={{
                height: 30,
                marginTop:5, 
              }}
            />
          </div>
        </div>

        <Editable
          renderElement={renderElement}
          renderLeaf={renderLeaf}
          decorate={decorate}
          placeholder="输入标题"
          spellCheck
          autoFocus
          renderPlaceholder={({ children, attributes }) => (
            <div {...attributes}>
              <p>{children}</p>
              <pre>
                Use the renderPlaceholder prop to customize rendering of the
                placeholder
              </pre>
            </div>
          )}
          className="content"
          onKeyDown={(event) => {
            handlerKeyDown(event, editor);
          }}
        />
      </Slate>
    </>
  );
};

const RemoveLinkButton = () => {
  const editor = useSlate();
  return (
    <Btn
      type="unlink"
      onClick={(event) => {
        event.preventDefault();
        if (isLinkActive(editor)) {
          unwrapLink(editor);
        }
      }}
    ></Btn>
  );
};
const unwrapLink = (editor) => {
  Transforms.unwrapNodes(editor, {
    match: (n) =>
      !Editor.isEditor(n) &&
      SlateElement.isElement(n) &&
      (n as Elements).type === "link",
  });
};

const LinkButton = () => {
  const editor = useSlate();
  return (
    <Btn
      type="link"
      onClick={(event) => {
        event.preventDefault();
        const url = window.prompt("Enter the URL of the link:");
        if (!url) return;
        if (editor.selection) {
          insertLink(editor, url);
        }
      }}
    ></Btn>
  );
};
const insertLink = (editor, url) => {
  if (editor.selection) {
    wrapLink(editor, url);
  }
};
const isLinkActive = (editor) => {
  const [link] = Editor.nodes(editor, {
    match: (n) =>
      !Editor.isEditor(n) &&
      SlateElement.isElement(n) &&
      (n as Elements).type === "link",
  });
  return !!link;
};
const wrapLink = (editor, url) => {
  if (isLinkActive(editor)) {
    unwrapLink(editor);
  }
  const { selection } = editor;
  const isCollapsed = selection && Range.isCollapsed(selection);
  const link: LinkElement = {
    type: "link",
    url,
    children: isCollapsed ? [{ text: url }] : [],
  };

  if (isCollapsed) {
    Transforms.insertNodes(editor, link);
  } else {
    Transforms.wrapNodes(editor, link, { split: true, at: selection });
    Transforms.collapse(editor, { edge: "end" });
  }
};

const handlerKeyDown = (event, editor) => {
  for (const hotkey in HOTKEYS) {
    if (isHotkey(hotkey, event as any)) {
      event.preventDefault();
      const mark = HOTKEYS[hotkey];
      toggleMark(editor, mark);
    }
  }
};
const BlockButton = ({ type }) => {
  const editor = useSlate();
  return (
    <Btn
      type={type}
      onClick={(event) => {
        event.preventDefault();
        toggleBlock(editor, type);
      }}
    ></Btn>
  );
};

const MarkButton = ({ type }) => {
  const editor = useSlate();
  return (
    <Btn
      type={type}
      onClick={(event) => {
        event.preventDefault();
        toggleMark(editor, type);
      }}
    ></Btn>
  );
};

const toggleBlock = (editor, format) => {
  const isActive = isBlockActive(editor, format);
  const isList = LIST_TYPES.includes(format);

  Transforms.unwrapNodes(editor, {
    match: (n) =>
      LIST_TYPES.includes(
        !Editor.isEditor(n) &&
          SlateElement.isElement(n) &&
          (n as EditorElement).type
      ),
    split: true,
  });
  const newProperties: Partial<EditorElement> = {
    type: isActive ? "paragraph" : isList ? "li" : format,
  };
  Transforms.setNodes(editor, newProperties);

  if (!isActive && isList) {
    const block = { type: format, children: [] };
    Transforms.wrapNodes(editor, block);
  }
};

const toggleMark = (editor, format) => {
  const isActive = isMarkActive(editor, format);
  if (isActive) {
    Editor.removeMark(editor, format);
  } else {
    Editor.addMark(editor, format, true);
  }
};

const isBlockActive = (editor, format) => {
  const [match] = Editor.nodes(editor, {
    match: (n) => {
      return (
        !Editor.isEditor(n) &&
        SlateElement.isElement(n) &&
        (n as EditorElement).type === format
      );
    },
  });

  return !!match;
};

const isMarkActive = (editor, format) => {
  const marks = Editor.marks(editor);
  return marks ? marks[format] === true : false;
};

const Element = (props) => {
  const { attributes, children, element } = props;
  switch (element.type) {
    case "quote":
      return <blockquote {...attributes}>{children}</blockquote>;
    case "ul":
      return <ul {...attributes}>{children}</ul>;
    case "h1":
      return <h1 {...attributes}>{children}</h1>;
    case "li":
      return <li {...attributes}>{children}</li>;
    case "ol":
      return <ol {...attributes}>{children}</ol>;
    case "title":
      return (
        <h2
          rows={1}
          {...attributes}
          style={{ display: "block" }}
          placeholder="请输入标题"
        >
          {children}
        </h2>
      );
    case "check":
      return <CheckListItem {...props} />;
    case "image":
      return <Image {...props} />;
    case "link":
      return (
        <a {...attributes} href={element.url}>
          {children}
        </a>
      );
    default:
      return <p {...attributes}>{children}</p>;
  }
};

const Leaf = ({ attributes, children, leaf }) => {
  if (leaf.bold) {
    children = <strong>{children}</strong>;
  }

  if (leaf.code) {
    children = <code>{children}</code>;
  }

  if (leaf.italic) {
    children = <em>{children}</em>;
  }
  if (leaf.delete) {
    children = <del>{children}</del>;
  }

  if (leaf.underline) {
    children = <u>{children}</u>;
  }

  return (
    <span
      style={{ backgroundColor: leaf.highlight && "#ffeeba" }}
      {...attributes}
    >
      {children}
    </span>
  );
};

const initialValue: EditorElement[] = [
  {
    type: "title",
    children: [{ text: "Enforce Your Layout!" }],
  },
  {
    type: "paragraph",
    children: [
      { text: "This is editable " },
      { text: "rich", bold: true },
      { text: " text, " },
      { text: "much", italic: true },
      { text: " better than a " },
      { text: "<textarea>", code: true },
      { text: "!" },
    ],
  },
  {
    type: "paragraph",
    children: [
      {
        text: "Since it's rich text, you can do things like turn a selection of text ",
      },
      { text: "bold", bold: true },
      {
        text: ", or add a semantically rendered block quote in the middle of the page, like this:",
      },
    ],
  },
  {
    type: "block-quote",
    children: [{ text: "A wise quote." }],
  },
  {
    type: "check",
    checked: true,
    children: [{ text: "Slide to the right." }],
  },
  {
    type: "check",
    checked: false,
    children: [{ text: "Criss-cross." }],
  },
  {
    type: "image",
    url: "https://source.unsplash.com/kFrdX5IeQzI",
    children: [{ text: "" }],
  },
  {
    type: "paragraph",
    children: [
      {
        text: "In addition to block nodes, you can create inline nodes, like ",
      },
      {
        type: "link",
        url: "https://en.wikipedia.org/wiki/Hypertext",
        children: [{ text: "hyperlinks" }],
      },
      {
        text: "!",
      },
    ],
  },
  {
    type: "paragraph",
    children: [
      {
        text: "This example shows hyperlinks in action. It features two ways to add links. You can either add a link via the toolbar icon above, or if you want in on a little secret, copy a URL to your keyboard and paste it while a range of text is selected.",
      },
    ],
  },
];

export default Edit;
